feat(Builder): a merger registry, and one entry per key in the Specification - #2220
Merged
Merged
Conversation
DerManoMann
force-pushed
the
feat/merge-pass
branch
2 times, most recently
from
September 30, 2026 00:05
ac28b89 to
10a431b
Compare
DerManoMann
marked this pull request as ready for review
September 30, 2026 00:19
`setLogger()` via `LoggerAwareInterface`, so a logger can be handed over after the pipeline was built — the builder has the build's logger only by then, and a `setLogger()` call after `withAugmenters()` used to leave the pipeline on the null logger.
…ication Two attributes claiming one key reached the compiler together, which wrote both into a PHP array and kept whichever it wrote last. Nothing said so outside the component buckets, and it was not even one rule: path items folded with `+`, so for those the *first* won. `Augmenter\Merge` decides instead. It groups each root collection on the claiming merger's `identity()`, folds each group in producer order and writes one entry per key back, so the compiler never sees a collision. It runs first in the reduce phase, the earliest point every identity exists, and again as the last default pipe, for what a late augmenter added. `Merge\LastWins` is the catch-all: it claims every type, keys each collection the way the document does — component key, path and method, webhook and method, path, tag name — and on a collision keeps the later entry and warns naming both halves. Positional lists have no key and pass through. `Builder::withMergers()` configures the chain, the fourth default the builder holds through a hook of its own. Every attribute gets `getMeta()`/`setMeta()`, a keyed store for whoever extends swagger-php: a package contributing through a hook marks its own attributes and its merger reads the mark back to decide precedence. Nothing in `src/` writes or reads it, and a test keeps it that way. A spec build now collects what its pipeline logs, so a merger's warning reaches `Result`; the compiler keeps its own logger and nothing is collected twice. One output change: a `PathItem` against a `PathItem` for one path moves from first-wins to last-wins, the same rule as everything else. A collision inside one attribute is left alone: those halves were written in one place by one author, and the compiler keeps the last of them as it always has.
DerManoMann
force-pushed
the
feat/merge-pass
branch
from
October 1, 2026 23:12
10a431b to
d3a3e7f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Two attributes can claim one key. Two operations on the same path and method, two schemas named
Pet— a scan finds one, awithSpecification()hook contributes the other, an inheritance clone makes a third. Nothing decided between them, so the compiler decided by accident: it writes each into a PHP array and keeps whichever it wrote last, silently. Classic reports the same collision as an error.It was not even one rule.
compilePaths()writes operations with=and folds path items in with+, so the last operation for a path and method won while the first path item for a path did. One map, one build, opposite rules, nothing reported either way. Webhooks were the quietest case: same last-wins, and no diagnostic of any kind, since a webhook operation gets nooperationIdand so never trips the accidental uniqueness warning that operations happen to have.This gives the decision an owner. A merger says what makes two attributes the same one and which survives; the pass applies the chain to the
Specification's own collections and writes one entry per key back, so what reaches the compiler has no collisions left and the compiler's incidental rules stop deciding anything. The shipped merger keeps the later entry and says so, which is what was happening anyway — now stated once, for every collection with a key, and reported.Only the root collections. That is where the halves come from different places and something has to choose. Two entries inside one attribute were written in one place by one author, so the compiler keeps the last of them as it always has.
Warnings a spec build produced were also being dropped:
Resultcarried the compiler's diagnostics and nothing the pipeline said, so a merger's report would have gone nowhere.Changes
Contracts\MergerInterface—supports(),identity(),merge(); a chain, first match wins, the shapeResolverInterfacehasAugmenter\Merge— the pass, first in the reduce phase and again as the last default pipeMerge\LastWins— the catch-all, keying each root collection the way the document does and reporting a collision with both locationsBuilder::withMergers()andgetDefaultMergers(), ordered byUtils\TypedListlike the augmentersgetMeta()/setMeta()on every attribute: a keyed store for whoever extends swagger-php, so a contributing package can mark its own attributes and have its merger recognise them; nothing insrc/reads or writes it, and a test keeps it that wayUtils\Pipelinetakes a logger after constructionBuildercollects what the spec pipeline logs intoResultcompilePaths()documents what its union still decides now that duplicate path items are goneBuilderreference; the reference pages list the shipped merger and the new pipeOne output change: a
PathItemagainst aPathItemfor one path moves from first-wins to last-wins, the same rule as everything else. Nothing can have relied on it, since neither behaviour was documented or reported.